#include <objbase.h>
//#include <string>
#include "cmdline.hpp"
#include <iostream>
#include <iomanip>
#include <algorithm>
#include <cassert>
#include <cstring>
#include <windows.h>
#include <uuids.h>
#include "versionhandling.hpp"
using std::wcout;
using std::endl;
using std::boolalpha;
using std::wstring;
using std::dec;

//from vtsreadfilter.cpp:
// {AEEA8CAC-9FDA-49ca-8001-1B984E8F1663}
static const CLSID CLSID_VTSRead = 
{ 0xaeea8cac, 0x9fda, 0x49ca, { 0x80, 0x1, 0x1b, 0x98, 0x4e, 0x8f, 0x16, 0x63 } };


CmdLine::CmdLine() :
    m_input(0),
    m_output(0),
    m_usage(false),
    m_list(false),
    m_version(false),
    m_reader(CLSID_VTSRead),
    m_splitter(CLSID_MPEG2Demultiplexer),
    m_script(false),
    m_verbose(false)
{
}

int CmdLine::Parse(int argc, wchar_t* argv[])
{    
    m_argv = argv;
    
    if (argc <= 1)  //program-name only
    {
        PrintUsage();
        return 1;  //soft error
    }
    
    //TODO:
    //start time
    //stop time
    //THESE REQUIRE CHANGES TO MUXER
    //
    //more ideas:
    //require video outpin 
    //require audio outpin
    //make video connection optional
    //make audio connection optional
    //verbose mode
    //terse mode
    //quiet
    //very quite
    //warnings vs. errors
    //treat warnings as errors
    
    wchar_t** const argv_end = argv + argc;
    assert(*argv_end == 0);
    
    --argc;  //throw away program name
    assert(argc >= 1);
    
    wchar_t** i = argv + 1;
    assert(i < argv_end);
    
    for (;;)
    {
        const int n = Parse(i);
        
        if (n < 0)  //error
            return n;
            
        if (n == 0)  //arg, not switch
        {
            --argc;
            assert(argc >= 0);
            
            ++i;
            assert(i <= argv_end);
        }
        else  //switch
        {
            argc -= n;
            assert(argc >= 0);
            assert((i + n) <= argv_end);

            std::rotate(i, i + n, argv_end);
        }
        
        if (argc <= 0)
            break;
    }
    
    wchar_t** const j = i;  //args end
    i = argv + 1;           //args begin
    
    if (m_usage)
    {
        PrintUsage();
        return 1;  //soft error
    }
    
    if (m_version)
    {
        PrintVersion();
        return 1;  //soft error
    }
            
    if (m_input == 0)  //not specified as switch
    {
        if (i >= j)  //no args remain
        {
            wcout << "No input filename specified." << endl;
            return 1;  //error
        }
        
        m_input = *i++;
        assert(m_input);
    }
    
    if (m_output == 0)  //not specified as switch
    {
        if (i >= j)  //no args remain
        {
            wcout << "No output filename specified." << endl;
            return 1;  //error
        }
        
        m_output = *i++;
        assert(m_output);
    }
    
    if (i < j)  //not all args consumed
    {
        wcout << L"Too many command-line arguments." << endl;
        return 1;
    }
    
    if (m_list)
    {
        ListArgs();
        return 1;  //soft error
    }
    
    return 0;
}


int CmdLine::Parse(wchar_t** i)
{
    assert(i);

    const wchar_t* arg = *i;
    assert(arg);
    
    switch (*arg)
    {
        case L'/':  //windows-style switch
            return ParseWindows(i);
            
        case L'-':  //unix-style switch
            if (*++arg == L'-')  //long-form
                return ParseLong(i);
            else                
                return ParseShort(i);
            
        default:
            return 0;  //this is an arg, not a switch
    }
}


int CmdLine::ParseWindows(wchar_t** i)
{
    assert(i);
    
    const wchar_t* arg = *i;
    assert(arg);
    assert(*arg == L'/');
    
    ++arg;

    if (*arg == L'\0')
    {
        wcout << L"Slash cannot stand alone as switch indicator." 
              << endl;
              
        return -1;  //error
    }
    
    const wchar_t* end = arg;
    
    while ((*end != L'\0') && (*end != L':'))
        ++end;

    const ptrdiff_t len = end - arg;
    
    if (wcsncmp(arg, L"input", len) == 0)
    {
        if (*end == L':')
        {
            m_input = ++end;
            
            if (wcslen(m_input) == 0)
            {
                wcout << "Empty value specified for input filename switch." << endl;
                return -1;  //error
            }

            return 1;
        }
        
        m_input = *++i;
        
        if (m_input == 0)        
        {
            wcout << "No filename specified for input switch." << endl;
            return -1;  //error
        }
        
        return 2;        
    }
    
    if (wcsncmp(arg, L"output", len) == 0)
    {
        if (*end == L':')
        {
            m_output = ++end;
            
            if (wcslen(m_output) == 0)
            {
                wcout << "Empty value specified for output filename switch." << endl;
                return -1;  //error
            }
            
            return 1;
        }
        
        m_output = *++i;
        
        if (m_output == 0)
        {
            wcout << "No filename specified for output switch." << endl;
            return -1;  //error
        }
        
        return 2;
    }
    
    if (_wcsnicmp(arg, L"reader", len) == 0)
    {
        const wchar_t* str;
        int n;
        
        if (*end == L':')
        {
            str = ++end;
            n = 1;
        }
        else
        {
            str = *++i;
            
            if (str == 0)
            {
                wcout << "No value specified for reader switch." << endl;
                return -1;  //error
            }
            
            n = 2;
        }

        const size_t len = wcslen(str);
        
        if (len == 0)
        {
            wcout << "Empty value specified for reader switch." << endl;
            return -1;  //error
        }
        
        if ((_wcsnicmp(str, L"VTSREAD", len) == 0) ||
            (_wcsnicmp(str, L"MJH.VTSREAD", len) == 0))
        {
            m_reader = CLSID_VTSRead;
        }
        else if (_wcsnicmp(str, L"AsyncReader", len) == 0)
        {
            m_reader = CLSID_AsyncReader;
        }
        else
        {
            wcout << L"Unknown reader value: " << str << endl;
            return -1;  //error
        }
        
        return n;
    }
    
    if (_wcsnicmp(arg, L"splitter", len) == 0)
    {
        const wchar_t* str;
        int n;
        
        if (*end == L':')
        {
            str = ++end;
            n = 1;
        }
        else
        {
            str = *++i;
            
            if (str == 0)
            {
                wcout << "No value specified for splitter switch." << endl;
                return -1;  //error
            }
            
            n = 2;
        }

        const size_t len = wcslen(str);
        
        if (len == 0)
        {
            wcout << "Empty value specified for splitter switch." << endl;
            return -1;  //error
        }
        
        if (_wcsnicmp(str, L"MMSPLITTER", len) == 0)
            m_splitter = CLSID_MMSPLITTER;
            
        else if (_wcsnicmp(str, L"MPEG2Demultiplexer", len) == 0)
            m_splitter = CLSID_MPEG2Demultiplexer;
            
        else
        {
            wcout << L"Unknown splitter value: " << str << endl;
            return -1;  //error
        }
        
        return n;
    }
    
    if (_wcsnicmp(arg, L"script-mode", len) == 0)
    {
        if (*end == L':')
        {
            wcout << "Script-mode option does not accept a value." << endl;
            return -1;  //error
        }
        
        m_script = true;
        return 1;
    }
        
    if (_wcsnicmp(arg, L"list", len) == 0)
    {
        if (*end == L':')
        {
            wcout << "List option does not accept a value." << endl;
            return -1;  //error
        }
        
        m_list = true;
        return 1;
    }
    
    if ((wcsncmp(arg, L"?", len) == 0) ||
        (_wcsnicmp(arg, L"help", len) == 0))
    {
        if (*end == L':')
        {
            wcout << "Help option does not accept a value." << endl;
            return -1;  //error
        }
        
        m_usage = true;
        return 1;
    }
    
    if (wcsncmp(arg, L"??", len) == 0)
    {
        if (*end == L':')
        {
            wcout << "Help option does not accept a value." << endl;
            return -1;  //error
        }
        
        m_usage = true;
        m_verbose = true;
        return 1;
    }
    
    if (_wcsnicmp(arg, L"usage", len) == 0)
    {
        if (*end == L':')
        {
            wcout << "Usage option does not accept a value." << endl;
            return -1;  //error
        }
        
        m_usage = true;
        return 1;
    }

#if 0    
    if (len == 1)
    {    
        if (*arg == L'v')
        {
            if (*end == L':')
            {
                wcout << "Version option does not accept a value." << endl;
                return -1;  //error
            }
            
            m_version = true;
            return 1;
        }
                    
        if (*arg == L'V')  //verbose
        {
            if (*end == L':')
            {
                wcout << "Verbose option does not accept a value." << endl;
                return -1;  //error
            }
            
            m_verbose = true;
            return 1;
        }
        
        //fall through to handle unknown switch
    }
    else if (_wcsnicmp(arg, L"version", len) == 0)
    {
        if (*end == L':')
        {
            wcout << "Version option does not accept a value." << endl;
            return -1;  //error
        }
        
        m_version = true;
        return 1;
    }
    else if (_wcsnicmp(arg, L"verbose", len) == 0)
    {
        if (*end == L':')
        {
            wcout << "Verbose option does not accept a value." << endl;
            return -1;  //error
        }
        
        m_verbose = true;
        return 1;
    }
#else
    if (*arg == L'v')
    {
        if (_wcsnicmp(arg, L"version", len) != 0)
        {
            wcout << L"Unknown switch: " << *i << L'\n'
                  << L"If version info was desired, specify the /v[ersion] switch.\n"
                  << L"If verbosity was desired, specify the /V[erbose] switch."
                  << endl;
                  
            return -1;  //error            
        }

        if (*end == L':')
        {
            wcout << "Version option does not accept a value." << endl;
            return -1;  //error
        }
        
        m_version = true;
        return 1;
    }
                
    if (*arg == L'V')  //verbose
    {
        if (_wcsnicmp(arg, L"verbose", len) != 0)
        {
            wcout << L"Unknown switch: " << *i << L'\n'
                  << L"If verbosity was desired, specify the /V[erbose] switch.\n"
                  << L"If version info was desired, specify the /v[ersion] switch."
                  << endl;
                  
            return -1;  //error            
        }
        
        if (*end == L':')
        {
            wcout << "Verbose option does not accept a value." << endl;
            return -1;  //error
        }
        
        m_verbose = true;
        return 1;
    }
#endif

    wcout << "Unknown switch: " << *i
          << "\nUse /help to get usage info."
          << endl;
          
    return -1;  //error
}


int CmdLine::ParseShort(wchar_t** i)
{
    assert(i);
        
    const wchar_t* arg = *i;
    assert(arg);
    assert(*arg == L'-');
    
    const wchar_t c = *++arg;
    assert(c != L'-');
    
    if (c == L'\0')
    {
        wcout << L"Hyphen cannot stand alone as switch indicator."
              << endl;
              
        return -1;
    }
    
    switch (c)
    {
        case L'i':
            if (*(arg + 1) != L'\0')
            {
                const size_t len = wcslen(arg);
                
                if (_wcsnicmp(arg, L"input", len) != 0)
                {
                    wcout << L"Unknown switch: " << *i
                          << L"\nUse -i or --input to specify input filename."
                          << endl;
                          
                    return -1;  //error
                }
            }
            
            m_input = *++i;
            
            if (m_input == 0)
            {
                wcout << L"No value specified for input filename switch."
                      << endl;
                      
                return -1;  //error
            }
            
            return 2;
            
        case L'o':
            if (*(arg + 1) != L'\0')
            {
                const size_t len = wcslen(arg);
                
                if (_wcsnicmp(arg, L"output", len) != 0)
                {
                    wcout << L"Unknown switch: " << *i
                          << L"\nUse -o or --output to specify output filename."
                          << endl;
                          
                    return -1;  //error
                }
            }
            
            m_output = *++i;
            
            if (m_output == 0)
            {
                wcout << L"No value specified for output filename switch."
                      << endl;
                      
                return -1;  //error
            }
                
            return 2;
            
        case L'r':
        case L'R':
        {
            if (*(arg + 1) != L'\0')
            {
                const size_t len = wcslen(arg);
                
                if (_wcsnicmp(arg, L"reader", len) != 0)
                {
                    wcout << L"Unknown switch: " << *i
                          << L"\nUse -r or --reader to specify reader filter."
                          << endl;
                          
                    return -1;  //error
                }
            }

            const wchar_t* const str = *++i;
        
            if (str == 0)
            {
                wcout << "No value specified for reader switch." << endl;
                return -1;  //error
            }
            
            const size_t len = wcslen(str);

            if ((_wcsnicmp(str, L"VTSREAD", len) == 0) ||
                (_wcsnicmp(str, L"MJH.VTSREAD", len) == 0))
            {
                m_reader = CLSID_VTSRead;
            }
            else if (_wcsnicmp(str, L"AsyncReader", len) == 0)
            {
                m_reader = CLSID_AsyncReader;
            }
            else
            {
                wcout << L"Unknown reader value: " << str << endl;
                return -1;  //error
            }
        
            return 2;
        }                    
        case L's':
        case L'S':
        {
            if (*(arg + 1) != L'\0')
            {
                const size_t len = wcslen(arg);
                
                if (_wcsnicmp(arg, L"splitter", len) != 0)
                {
                    wcout << L"Unknown switch: " << *i
                          << L"\nUse -s or --splitter to specify splitter filter."
                          << endl;
                          
                    return -1;  //error
                }
            }

            const wchar_t* const str = *++i;
        
            if (str == 0)
            {
                wcout << "No value specified for splitter switch." << endl;
                return -1;  //error
            }
            
            const size_t len = wcslen(str);
            
            if (_wcsnicmp(str, L"MMSPLITTER", len) == 0)
                m_splitter = CLSID_MMSPLITTER;
                
            else if (_wcsnicmp(str, L"MPEG2Demultiplexer", len) == 0)
                m_splitter = CLSID_MPEG2Demultiplexer;
                
            else
            {
                wcout << L"Unknown splitter value: " << str << endl;
                return -1;  //error
            }
            
            return 2;
        }            
        case L'?':
            ++arg;  //throw away '?'
            
            if (*arg == L'?')  //-??
            {
                m_verbose = true;
                ++arg;
            }

            if (*arg != L'\0')
            {
                wcout << L"Unknown switch: " << *i
                      << L"\nUse -? by itself to get usage info."
                      << endl;
                      
                return -1;
            }
            
            m_usage = true;
            return 1;
            
        case L'h':
        case L'H':
            if (*(arg + 1) != L'\0')
            {
                const size_t len = wcslen(arg);
                
                if (_wcsnicmp(arg, L"help", len) != 0)
                {
                    wcout << L"Unknown switch: " << *i
                          << L"\nIf help info was desired, specify the -h or --help switches."
                          << endl;
                          
                    return -1;            
                }
            }
            
            m_usage = true;
            return 1;
            
        case L'u':
        case L'U':
            if (*(arg + 1) != L'\0')
            {
                const size_t len = wcslen(arg);
                
                if (_wcsnicmp(arg, L"usage", len) != 0)
                {
                    wcout << L"Unknown switch: " << *i
                          << L"\nIf usage info was desired, specify the -u or --usage switches."
                          << endl;
                          
                    return -1;
                }
            }            
                
            m_usage = true;
            return 1;

        case L'l':
        case L'L':
            if (*(arg + 1) != L'\0')
            {
                const size_t len = wcslen(arg);
                
                if (_wcsnicmp(arg, L"list", len) != 0)
                {
                    wcout << L"Unknown switch: " << *i
                          << L"\nIf list info was desired, specify the -l or --list switches."
                          << endl;
                          
                    return -1;  //error
                }
            }
                
            m_list = true;
            return 1;
                            
        case L'v':
            if (*(arg + 1) != L'\0')
            {
                const size_t len = wcslen(arg);
                
                if (_wcsnicmp(arg, L"version", len) != 0)
                {
                    wcout << "Unknown switch: " << *i
                          << L"\nIf version info was desired, specify the -v or --version switches."
                          << L"\nIf verbosity was desired, specify the -V or --verbose switches."
                          << endl;
                          
                    return -1;  //error            
                }
            }
                
            m_version = true;
            return 1;
            
        case L'V':
            if (*(arg + 1) != L'\0')
            {
                const size_t len = wcslen(arg);
                
                if (_wcsnicmp(arg, L"verbose", len) != 0)
                {
                    wcout << "Unknown switch: " << *i
                          << L"\nIf verbosity was desired, specify the -V or --verbose switches."
                          << L"\nIf version info was desired, specify the -v or --version switches."
                          << endl;
                          
                    return -1;  //error            
                }
            }
                
            m_verbose = true;
            return 1;

        default:
            wcout << L"Unknown switch: " << *i
                  << L"\nUse -h to get usage info." 
                  << endl;
                  
            return -1;
    }        
}


int CmdLine::ParseLong(wchar_t** i)
{
    assert(i);
    
    const wchar_t* arg = *i;
    assert(arg);
    assert(*arg == L'-');
    
    ++arg;
    assert(arg);
    assert(*arg == L'-');
    
    const wchar_t* end = ++arg;
    
    while ((*end != L'\0') && (*end != L'='))
        ++end;
        
    const size_t len = end - arg;
    
    if (len == 0)
    {
        wcout << L"Double-hyphen cannot stand alone as switch indicator." 
              << endl;
              
        return -1;  //error
    }
    
    if (wcsncmp(arg, L"input", len) == 0)
    {
        if (*end)
        {
            assert(*end == L'=');
            m_input = ++end;
            
            if (wcslen(m_input) == 0)
            {
                wcout << "Empty value specified for input filename switch." << endl;
                return -1;  //error
            }
            
            return 1;
        }

        m_input = *++i;
        
        if (m_input == 0)
        {
            wcout << "No filename specified for input switch." << endl;
            return -1;  //error
        }
        
        return 2;
    }

    if (wcsncmp(arg, L"output", len) == 0)
    {
        if (*end)
        {
            assert(*end == L'=');
            m_output = ++end;
            
            if (wcslen(m_output) == 0)
            {
                wcout << "Empty value specified for output filename switch." << endl;
                return -1;  //error
            }
            
            return 1;
        }

        m_output = *++i;
        
        if (m_output == 0)
        {
            wcout << "No filename specified for output switch." << endl;
            return -1;  //error
        }
        
        return 2;
    }
    
    if (wcsncmp(arg, L"reader", len) == 0)
    {
        const wchar_t* str;
        int n;
    
        if (*end)
        {
            assert(*end == L'=');
            str = ++end;
            n = 1;
        }
        else
        {
            str = *++i;
            
            if (str == 0)
            {
                wcout << L"No value specified for reader switch." << endl;
                return -1;  //error
            }
            
            n = 2;
        }
        
        const size_t len = wcslen(str);
        
        if (len == 0)
        {
            wcout << "Empty value specified for reader switch." << endl;
            return -1;  //error
        }
        
        if ((_wcsnicmp(str, L"VTSREAD", len) == 0) ||
            (_wcsnicmp(str, L"MJH.VTSREAD", len) == 0))
        {
            m_reader = CLSID_VTSRead;
        }
        else if (_wcsnicmp(str, L"AsyncReader", len) == 0)
        {
            m_reader = CLSID_AsyncReader;
        }
        else
        {
            wcout << L"Unknown reader value: " << str << endl;
            return -1;  //error
        }
        
        return n;
    }
    
    if (wcsncmp(arg, L"splitter", len) == 0)
    {
        const wchar_t* str;
        int n;
    
        if (*end)
        {
            assert(*end == L'=');
            str = ++end;
            n = 1;
        }
        else
        {
            str = *++i;

            if (str == 0)
            {
                wcout << L"No value specified for splitter switch." << endl;
                return -1;  //error
            }

            n = 2;
        }
        
        const size_t len = wcslen(str);
        
        if (len == 0)
        {
            wcout << "Empty value specified for splitter switch." << endl;
            return -1;  //error
        }
        
        if (_wcsnicmp(str, L"MMSPLITTER", len) == 0)
            m_splitter = CLSID_MMSPLITTER;
            
        else if (_wcsnicmp(str, L"MPEG2Demultiplexer", len) == 0)
            m_splitter = CLSID_MPEG2Demultiplexer;
            
        else
        {
            wcout << L"Unknown splitter value: " << str << endl;
            return -1;  //error
        }
        
        return n;
    }
    
    if (_wcsnicmp(arg, L"script-mode", len) == 0)
    {
        if (*end == L'=')
        {
            wcout << "Script-mode switch does not accept a value." << endl;
            return -1;  //error
        }
        
        m_script = true;
        return 1;
    }
    
    if (_wcsnicmp(arg, L"help", len) == 0)
    {
        if (*end == L'=')
        {
            wcout << "Help switch does not accept a value." << endl;
            return -1;  //error
        }
        
        m_usage = true;
        return 1;
    }
    
    if (wcsncmp(arg, L"?", len) == 0)
    {
        if (*end == L'=')
        {
            wcout << "Help switch does not accept a value." << endl;
            return -1;  //error
        }
        
        m_usage = true;
        m_verbose = true;
        return 1;
    }
    
    if (_wcsnicmp(arg, L"usage", len) == 0)
    {
        if (*end == L'=')
        {
            wcout << "Usage switch does not accept a value." << endl;
            return -1;  //error
        }
        
        m_usage = true; 
        return 1;
    }
    
    if (_wcsnicmp(arg, L"list", len) == 0)
    {
        if (*end == L'=')
        {
            wcout << L"List switch does not accept a value." << endl;
            return -1;  //error
        }
        
        m_list = true;
        return 1;
    }
    
    if (_wcsnicmp(arg, L"version", len) == 0)
    {
        if (*end == L'=')
        {
            wcout << L"Version switch does not accept a value." << endl;
            return -1;  //error
        }
        
        m_version = true;
        return 1;
    }
    
    if (_wcsnicmp(arg, L"verbose", len) == 0)
    {
        if (*end == L'=')
        {
            wcout << L"Verbose switch does not accept a value." << endl;
            return -1;  //error
        }
        
        m_verbose = true;
        return 1;
    }

    wcout << "Unknown switch: " << *i
          << "\nUse --help to get usage info."
          << endl;
          
    return -1;  //error
}


const wchar_t* CmdLine::GetInputFileName() const
{
    return m_input;
}


const wchar_t* CmdLine::GetOutputFileName() const
{
    return m_output;
}


const CLSID& CmdLine::GetReader() const
{
    return m_reader;
}


const CLSID& CmdLine::GetSplitter() const
{
    return m_splitter;
}


bool CmdLine::ScriptMode() const
{
    return m_script;
}


void CmdLine::PrintVersion() const
{
    wcout << "vob2mkv ";
    VersionHandling::GetVersion(wcout);
    wcout << L'\n';
}


void CmdLine::PrintUsage() const
{
    wcout << L"usage: vob2mkv <opts> <args>\n";
            
    wcout << L"  -i, --input       input filename\n"
          << L"  -o, --output      output filename\n"
          << L"  -r, --reader      reader identifier\n"
          << L"  -s, --splitter    splitter identifier\n"
          << L"  --script-mode     print progress in script-friendly way\n"
          << L"  -l, --list        print switch values, but do not run app\n"
          << L"  -v, --version     print version information\n"
          << L"  -V, --verbose     print verbose list or usage info\n"
          << L"  -?, -h, --help    print usage\n"
          << L"  -??, --?          print verbose usage\n";
    
    if (m_verbose)
    {
        wcout << L'\n'
              << L"The possible values for --reader are:\n"
              << L" VTSRead (default)\n"
              << L" AsyncReader\n"
              << L'\n'
              << L"The possible values for --splitter are:\n"
              << L" MPEG2Demultiplexer (default)\n"
              << L" MMSPLITTER\n"
              << L'\n'
              << L"The values listed above are case-insensitive, and may\n"
              << L"be abbreviated.\n";

        wcout << L'\n'
              << L"The input and output filenames must each be specified,\n"
              << L"as either a switch value or as a command-line argument.\n"
              << L'\n'
              << L"Note that the order of appearance of switches and arguments\n"
              << L"on the command line does not matter.\n"
              << L'\n'
              << L"Switches may also be specified using Windows-style syntax,\n"
              << L"using a forward slash for the switch.\n";
    }

    wcout << endl;          
}


void CmdLine::ListArgs() const
{
    wcout << L"input      : \"";
    
    if (m_verbose)
        wcout << GetPath(m_input);
    else
        wcout << m_input;
        
    wcout << L"\"\n";    
    wcout << L"output     : \"";
    
    if (m_verbose)
        wcout << GetPath(m_output);
    else
        wcout << m_output;
        
    wcout << L"\"\n";    
    wcout << L"reader     : ";
          
    if (m_reader == CLSID_VTSRead)
        wcout << L"VTSRead";        
    else if (m_reader == CLSID_AsyncReader)
        wcout << L"AsyncReader";
        
    if (m_verbose)
        wcout << L' ' << ToString(m_reader);
        
    wcout << L"\nsplitter   : ";
          
    if (m_splitter == CLSID_MPEG2Demultiplexer)
        wcout << L"MPEG2Demultiplexer";        
    else if (m_splitter == CLSID_MMSPLITTER)
        wcout << L"MMSPLITTER";
        
    if (m_verbose)
        wcout << L' ' << ToString(m_splitter);
        
    wcout << L"\nscript-mode: " << boolalpha << m_script << L'\n';
    
    VetInputFileName();
    
    wcout << endl;
}


std::wstring CmdLine::GetPath(const wchar_t* filename)
{
    assert(filename); 
    
    //using std::wstring;       
    //wstring path;
    //wstring::size_type filepos;
    
    DWORD buflen = _MAX_PATH + 1;
    
    for (;;)
    {
        const DWORD cb = buflen * sizeof(wchar_t);
        wchar_t* const buf = (wchar_t*)_malloca(cb);        
        wchar_t* ptr;
    
        const DWORD n = GetFullPathName(filename, buflen, buf, &ptr);
        
        if (n == 0)  //error
        {
            const DWORD e = GetLastError();
            e;
            return filename;  //best we can do
        }
        
        if (n < buflen)
        {
            //path = buf;
            //filepos = ptr - buf;            
            //break;
            return buf;
        }
        
        buflen = 2 * buflen + 1;
    }
    
    //return path;
}


std::wstring CmdLine::ToString(const GUID& g)
{
    enum { cch = 39 };
    wchar_t str[cch];
    
    const int n = StringFromGUID2(g, str, cch);
    assert(n == 39);
    
    return str;
}


void CmdLine::VetInputFileName() const
{
    if (!m_verbose)
        return;
        
    wcout << L'\n';
        
    wstring path;    
    wstring::size_type filepos;
    
    DWORD buflen = _MAX_PATH + 1;
    
    for (;;)
    {
        const DWORD cb = buflen * sizeof(wchar_t);
        wchar_t* const buf = (wchar_t*)_malloca(cb);        
        wchar_t* ptr;
    
        const DWORD n = GetFullPathName(m_input, buflen, buf, &ptr);
        
        if (n == 0)  //error
        {
            const DWORD e = GetLastError();
            
            wcout << L"Unable to vet input filename.\n"
                  << L"GetFullPathName failed. (" << dec << e << L")\n";
                  
            return;
        }
        
        if (n < buflen)
        {
            if (ptr == 0)  //filename points to a directory, not a file
            {
                wcout << L"Input filename specifies directory, not file.\n";
                return;
            }
            
            assert(ptr >= buf);
            
            path = buf;
            filepos = ptr - buf;
            
            break;
        }
        
        buflen = 2 * buflen + 1;
    }
    
    const wchar_t* const pathstr = path.c_str();
    const wchar_t* const file = pathstr + filepos;
    const wchar_t* const ext = wcsrchr(file, L'.');
    
    if (ext == 0)
    {
        wcout << L"Input filename does not have file extension.\n";
        return;
    }
        
    if (_wcsicmp(ext, L".vob") == 0)
        __noop;                
    else if (_wcsicmp(ext, L".mpg") == 0)
        __noop;        
    else
        wcout << L"Input filename has non-standard extension.\n";
        
    //012345678901
    //VTS_X_Y.VOB
    
    const size_t len = wcslen(file);
    
    if (len < 4)
    {
        wcout << L"Input filename is too short.\n";
        return;
    }
        
    if (_wcsnicmp(file, L"vts_", 4) != 0)
    {
        wcout << L"Input filename is not spelled \"VTS_...\".\n";
        return;
    }

    if (len < 11)
    {
        wcout << L"Input filename is too short.\n";
        return;
    }
        
    const wchar_t* const title = file + 4;        
    const wchar_t* str = wcschr(title, L'_');
    
    if ((str == 0) || ((str - title) <= 0))
    {
        wcout << L"Input filename title-part has bad syntax "
              << L"(no underscore found).\n";
              
        return;
    }
                
    const wchar_t* ptr = title;
    
    while (ptr != str)
    {
        if (!iswdigit(*ptr++))
        {
            wcout << L"Input filename title-part has bad syntax "
                  << L"(non-digit found).\n";
                  
            return;
        }
    }
    
    const wchar_t* const idxstr = str + 1;
    const ptrdiff_t idxlen = ext - idxstr;
    
    if (idxlen <= 0)
    {
        wcout << L"Input filename index-part has bad syntax "
              << L"(index not found).\n";
              
        return;
    }
        
    if (idxlen > 2)  //weird
    {
        wcout << L"Input filename index-part has bad syntax "
              << L"(index too long).\n";
              
        return;
    }
        
    ptr = idxstr;
    
    while (ptr != ext)
    {
        if (!iswdigit(*ptr++))
        {
            wcout << L"Input filename index-part has bad syntax "
                  << L"(non-digit found).\n";

            return;
        }
    }
    
    wcout << L"Input filename has correct syntax.\n";

    wstring re(pathstr, idxstr);
    
    re.append(idxlen, L'?');
    re.append(ext);
         
    WIN32_FIND_DATA fd;
       
    const HANDLE h = FindFirstFile(re.c_str(), &fd);
    
    if (h == INVALID_HANDLE_VALUE)
    {
        const DWORD e = GetLastError();
        
        if (e == ERROR_FILE_NOT_FOUND)
        {
            wcout << L"No files corresponding to search pattern found.\n";
            return;
        }        
        
        wcout << L"FindFirstFile failed. (" << dec << e << L")\n";
        return;
    }
    
    wcout << L"\nFiles that correspond to search pattern:\n";
    
    for (;;)
    {
        wcout << fd.cFileName << L'\n';
        
        const BOOL bMore = FindNextFile(h, &fd);
        
        if (bMore)
            continue;
            
        const DWORD e = GetLastError();
        
        if (e == ERROR_NO_MORE_FILES)
            break;
            
        wcout << L"FindNextFile failed. (" << dec << e << L")\n";
        break;
    }        

    const BOOL bClose = FindClose(h);
    assert(bClose);
    bClose;
}
